package embuzzled.g5;

import java.awt.Color;
import java.awt.Point;
import java.util.ArrayList;
import java.util.Random;

import embuzzled.ui.Grid;

public class PuzzleGrid 
{
	Color[][] grid;
	
	int rows, cols;
	
	public static final int MAX_PLACEMENT_ATTEMPTS = 100;
	
	Random r;
	
	String solution;
	
	int numPuzzles;
	
	public PuzzleGrid(Grid grid)
	{
		this.grid = generateInitialColorMap(grid);
		rows = grid.rows;
		cols = grid.cols;
		r = new Random();
		solution = "";
		numPuzzles = 0;
	}
	
	public void populate(PuzzleSet possiblePuzzles)
	{
		//TODO - choose puzzles to place. Try to place them.
		//       update puzzle count, solution
		
		System.out.println("Populating...");
		int numPuzzles = possiblePuzzles.getSize();
		
		// Naive - tries to add all puzzles
		for(int i = 0; i < numPuzzles; i++)
		{
			System.out.println("Attempting to add puzzle " + i);
			Puzzle p = possiblePuzzles.get(i);
			addPuzzle(p);
		}
	}
	
	/**
	 *  Attempts to add a puzzle to this grid.
	 *  Updates the color map and the solution if it
	 *  was added successfully.
	 * @param p A puzzle.
	 * @return True if it was added successfully. False otherwise.
	 */
	public boolean addPuzzle(Puzzle p)
	{
		Color[][] newGrid;
		
		if(p.isComposite())
		{
			System.out.println("Composite puzzle");
			newGrid = addCompositePuzzle(grid, p);
		}
		else
		{
			System.out.println("Standalone puzzle");
			newGrid = addStandalonePuzzle(grid, p);
		}
		
		if(newGrid != null)
		{
			System.out.println("Puzzle added!");
			grid = newGrid;
			solution = solution + p.getSolution() + "\n";
			numPuzzles++;
			return true;
		}
		else
		{
			System.out.println("Adding failed");
			return false;
		}
	}
	
	/**
	 * Attempts to add a standalone (non-composite) puzzle to the grid.
	 * Uses the placement strategies given by the puzzle.
	 * If adding can succeed, then this method returns a color map
	 * representing the current state of this grid with the puzzle added.
	 * If the puzzle cannot be added, then it returns null.
	 * @param grid A color map.
	 * @param p A non-composite puzzle.
	 * @return "grid" with the puzzle added if possible. Null if not.
	 */
	public Color[][] addStandalonePuzzle(Color[][] grid, Puzzle p)
	{
		int[] strats = p.getPlacementStrategies();
		Color[][] newMap = null;
		
		//Try all strategies in order, naive
		for(int currentStrat : strats)
		{
			if(currentStrat == Constants.PLACEMENT_ANYWHERE)
				newMap = randomPlacement(grid, p);
			
			if(newMap != null)
			{
				//Adding succeeded
				return newMap;
			}
				
		}
			
		return null; //No strategy worked
	}
	
	/**
	 * Try random spots on the grid to place the puzzle.
	 * Returns null if failed attempts exceeds MAX_PLACEMENT_ATTEMPTS.
	 * Returns the map with the puzzle added if it succeeds.
	 * @param grid A color map.
	 * @param p A non-composite puzzle.
	 * @return The grid with puzzle p added if possible. Null if not.
	 */
	public Color[][] randomPlacement(Color[][] grid, Puzzle p)
	{
		System.out.println("Random placement");
		Color[][] puzzleMap = p.returnLocalColorMap();
		Color[][] newMap = null;
		
		int attempts = 0;
		
		while(attempts < MAX_PLACEMENT_ATTEMPTS && newMap == null)
		{
			//Pick a random point in the grid
			//Needs to be updated later with knowledge of free space
			Point pt = new Point(r.nextInt(grid[0].length), r.nextInt(grid.length));
			
			//Attempt to superimpose the puzzle on the grid at that point
			//newMap != null when succeeds, so while loop terminates
			newMap = Helper.superimpose(grid, puzzleMap, pt);
			
			//Time out and return null after enough tries.
			attempts++;
		}
		
		if(newMap != null)
		{
			System.out.println("Random placement succeeded.");
		}
		
		return newMap;
	}
	
	public Color[][] addCompositePuzzle(Color[][] grid, Puzzle p)
	{
		int[] strats = p.getPlacementStrategies();
		Color[][] newMap = null;
		
		//Try all strategies in order, naive
		for(int currentStrat : strats)
		{
			if(currentStrat == Constants.PLACEMENT_HORIZONTAL)
				newMap = horizontalSequencePlacement(grid, p);
			else if(currentStrat == Constants.PLACEMENT_VERTICAL_DOWN)
				newMap = verticalDownSequencePlacement(grid, p);
			
			if(newMap != null)
			{
				//Adding succeeded
				return newMap;
			}
				
		}
			
		return null; //No strategy worked
	}
	
	
	/**
	 *  Tries to add a composite puzzle to the grid.
	 *  It picks a random cell in the first row, and tries to add the
	 *  first component. If that succeeds, it tries to add each successive
	 *  component down that column.
	 *  Else, it tries adding the first component exhaustively.
	 * @param grid A color map.
	 * @param p A composite puzzle.
	 * @return The grid containing this puzzle if possible. Null if not.
	 */
	public Color[][] verticalDownSequencePlacement(Color[][] grid, Puzzle p)
	{
		System.out.println("Vertical down sequence placement.");
		ArrayList<PuzzleComponent> components = p.getComponents();
		
		//Current row/col we are placing a component in
		int row = 0;
		int col = r.nextInt(cols);
		
		//Current component we are placing
		int comp = 0;
		
		//How many columns we have tried when placing the first component
		int colsTried = 0;
		
		//Has the puzzle been placed yet?
		boolean placed = false;
		
		PuzzleComponent currentComponent = components.get(comp);
		Color[][] currentComponentMap = currentComponent.returnLocalColorMap();
		
		Color[][] currentGrid = grid;
		Color[][] newGrid = grid;
		
		Point pt = new Point(col, row);
		
		while(!placed)
		{
			pt = new Point(col,row);
			newGrid = Helper.superimpose(currentGrid, currentComponentMap, pt);
			
			if(newGrid != null)
			{
				//Added component successfully.
				System.out.println("Added component " + comp + " to [" + row + ", " + col + "]");
				
				//Keep the new grid.
				currentGrid = newGrid;
				
				//Look at the row beneath the added com
				row += currentComponentMap.length + 1;
				comp += 1;
				
				//Added all components - return success
				if(comp == components.size())
				{
					System.out.println("All components successfully added.");
					return newGrid;
				}
					
				
				//else, prepare to add next component
				currentComponent = components.get(comp);
				currentComponentMap = currentComponent.returnLocalColorMap();
			}
			else if(newGrid == null && comp == 0 && colsTried < cols)
			{
				//Failed to add first component
				//Check next column, wrapping if necessary.
				//System.out.println("Failed to add first component to [" + row + ", " + col + "]");
				//System.out.println("Looking at next column");
				col = (col + 1) % cols;
				colsTried++;
			}
			else if(newGrid == null && comp == 0 && row < rows)
			{
				//Failed to add first component to each column in a row
				//System.out.println("Failed to add first component to row " + row);
				//System.out.println("Checking next row");
				//Check next row
				col = (col + 1) % cols;
				colsTried = 0;
				row++;
			}
			else if(newGrid == null && comp == 0 && row == rows)
			{
				//Cannot add first component, return null.
				System.out.println("Cannot add first component anywhere");
				return null;
			}
			else if(newGrid == null && row < rows)
			{
				//System.out.println("Cannot add component " + comp + " to row " + row);
				//Cannot add nth component.
				//Check next row.
				//Might want code to ensure equal spacing?
				row++;
			}
			else
			{
				//Cannot place all components.
				return null;
			}
		}
		
		
		return null;
	}
	
	/**
	 *  Tries to add a composite puzzle to the grid.
	 *  It picks a random cell in the first row, and tries to add the
	 *  first component. If that succeeds, it tries to add each successive
	 *  component down that column.
	 *  Else, it tries adding the first component exhaustively.
	 * @param grid A color map.
	 * @param p A composite puzzle.
	 * @return The grid containing this puzzle if possible. Null if not.
	 */
	public Color[][] horizontalSequencePlacement(Color[][] grid, Puzzle p)
	{
		System.out.println("Horizontal sequence placement.");
		ArrayList<PuzzleComponent> components = p.getComponents();
		
		//Current row/col we are placing a component in
		int row = r.nextInt(rows);
		int col = 0;
		
		//Current component we are placing
		int comp = 0;
		
		//How many columns we have tried when placing the first component
		int rowsTried = 0;
		
		//Has the puzzle been placed yet?
		boolean placed = false;
		
		PuzzleComponent currentComponent = components.get(comp);
		Color[][] currentComponentMap = currentComponent.returnLocalColorMap();
		
		Color[][] currentGrid = grid;
		Color[][] newGrid = grid;
		
		Point pt = new Point(col, row);
		
		while(!placed)
		{
			pt = new Point(col,row);
			newGrid = Helper.superimpose(currentGrid, currentComponentMap, pt);
			
			if(newGrid != null)
			{
				//Added component successfully.
				System.out.println("Added component " + comp + " to [" + row + ", " + col + "]");
				
				//Keep the new grid.
				currentGrid = newGrid;
				
				//Look at the row beneath the added com
				col += currentComponentMap[0].length + 1;
				comp += 1;
				
				//Added all components - return success
				if(comp == components.size())
				{
					System.out.println("All components successfully added.");
					return newGrid;
				}
					
				
				//else, prepare to add next component
				currentComponent = components.get(comp);
				currentComponentMap = currentComponent.returnLocalColorMap();
			}
			else if(newGrid == null && comp == 0 && rowsTried < rows)
			{
				//Failed to add first component
				//Check next column, wrapping if necessary.
				//System.out.println("Failed to add first component to [" + row + ", " + col + "]");
				//System.out.println("Looking at next column");
				row = (row + 1) % rows;
				rowsTried++;
			}
			else if(newGrid == null && comp == 0 && col < cols)
			{
				//Failed to add first component to each column in a row
				//System.out.println("Failed to add first component to row " + row);
				//System.out.println("Checking next row");
				//Check next row
				row = (row + 1) % rows;
				rowsTried = 0;
				col++;
			}
			else if(newGrid == null && comp == 0 && col == cols)
			{
				//Cannot add first component, return null.
				System.out.println("Cannot add first component anywhere");
				return null;
			}
			else if(newGrid == null && col < cols)
			{
				//System.out.println("Cannot add component " + comp + " to row " + row);
				//Cannot add nth component.
				//Check next row.
				//Might want code to ensure equal spacing?
				col++;
			}
			else
			{
				//Cannot place all components.
				return null;
			}
		}
		
		
		return null;
	}
	
	public Color[][] generateInitialColorMap(Grid grid)
	{
		int rows = grid.rows;
		int cols = grid.cols;
		
		Color[][] map = new Color[rows][cols];
		
		for(int i = 0; i < rows; i++)
			for(int j = 0; j < cols; j++)
			{
				if(grid.datagrid[i][j])
					map[i][j] = Constants.COLOR_OBSTACLE;
				else
					map[i][j] = Constants.COLOR_CLEAR;
			}
		
		return map;
	}
	
}
